Newer
Older
VolumeRendering_in_Unity / Assets / 6. Gradient / Shaders / VoumeRendering_kai.shader
Shader "VolumeRendering/VolumeRendering_kai"
{
	Properties
	{
		[Header(Rendering)]
		_Volume("Volume", 3D) = "" {}
		_Transfer("Transfer", 2D) = "" {}
		_Iteration("Iteration", Int) = 100
		_Intensity("Intensity", Range(0.0, 1.0)) = 0.1
		[Enum(UnityEngine.Rendering.BlendMode)] _BlendSrc("Blend Src", Float) = 5
		[Enum(UnityEngine.Rendering.BlendMode)] _BlendDst("Blend Dst", Float) = 10

		[Header(Ranges)]
		_MinX("MinX", Range(0, 1)) = 0.0
		_MaxX("MaxX", Range(0, 1)) = 1.0
		_MinY("MinY", Range(0, 1)) = 0.0
		_MaxY("MaxY", Range(0, 1)) = 1.0
		_MinZ("MinZ", Range(0, 1)) = 0.0
		_MaxZ("MaxZ", Range(0, 1)) = 1.0

		[Header(Variable)]
		[KeywordEnum(VARIABLE_LENGTH, FIXED_LENGTH)]
		_RAY("Ray Method", Float) = 0
	}

CGINCLUDE
#include "UnityCG.cginc"

struct appdata
{
	float4 vertex : POSITION;
};

struct v2f
{
	float4 vertex : SV_POSITION;
	float3 localPos : TEXCOORD0;
	float3 worldPos : TEXCOORD1;
	float3 localViewDir : TEXCOORD2;
	float3 worldViewDir : TEXCOORD3;
	float4 screenPos : TEXCOORD4;
};

struct Ray
{
	float3 from;
	float3 dir;
	float tmax;
};

sampler3D _Volume;
sampler2D _Transfer;
int _Iteration;
fixed _Intensity;
fixed _MinX, _MaxX, _MinY, _MaxY, _MinZ, _MaxZ;

#define INTEGRATION_THRESHOLD 0.01
#define DIRECTIONAL_EPSILON 0.0009765625
#define ALPHA_THRESHOLD 0.01

void intersection(inout Ray ray)
{
	float3 invDir = 1.0 / ray.dir;
	float3 t1 = (-0.5 - ray.from) * invDir;
	float3 t2 = (+0.5 - ray.from) * invDir;

	float3 tmax3 = max(t1, t2);
	float2 tmax2 = min(tmax3.xx, tmax3.yz);
	ray.tmax = min(tmax2.x, tmax2.y);
}

inline fixed sampleVolume(float3 pos)
{
	fixed x = step(pos.x, _MaxX) * step(_MinX, pos.x);
	fixed y = step(pos.y, _MaxY) * step(_MinY, pos.y);
	fixed z = step(pos.z, _MaxZ) * step(_MinZ, pos.z);
	return tex3D(_Volume, pos).a * (x * y * z);
}

inline fixed4 transferFunction(float t)
{
	return tex2D(_Transfer, float2(t, 0));
}

sampler2D _CameraDepthTexture;

// キューブに関するtransform.InverseTransformPoint
float3 worldToObjectPos(float3 worldPos) { return mul(unity_WorldToObject, float4(worldPos, 1.0)).xyz; }

// キューブに関するtransform.InverseTransformVector
float3 worldToObjectVec(float3 worldVec) { return mul((float3x3)unity_WorldToObject, worldVec); }

// キューブに関するtransform.TransformVector
float3 objectToWorldVec(float3 localVec) { return mul((float3x3)unity_ObjectToWorld, localVec); }

// カメラのtransform.position
float3 getWorldCameraPos() { return UNITY_MATRIX_I_V._14_24_34; }

// カメラのtransform.forward
float3 getWorldCameraDir() { return -UNITY_MATRIX_V._31_32_33; }

// カメラのnearClipPlane
float getWorldCameraNear() { return _ProjectionParams.y; }

// カメラ投影法が透視投影かどうか
bool isPerspective() { return any(UNITY_MATRIX_P._41_42_43); }

// カメラが透視投影ならカメラの位置、平行投影ならカメラ位置を通る平面上のworldPosを正面にとらえる位置
float3 getWorldCameraOrigin(float3 worldPos)
{
	return isPerspective()
		? getWorldCameraPos()
		: worldPos + dot(getWorldCameraPos() - worldPos, getWorldCameraDir()) * getWorldCameraDir();
}

// キューブのtransform.position
float3 getWorldBoundsCenter() { return unity_ObjectToWorld._14_24_34; }

// カメラの位置からキューブを見て、2枚の平面で前後にキューブを挟んだ時の長さを求める
float getWorldBoundsDepth(float3 worldCameraDir)
{
	float3 worldCornerVec1 = objectToWorldVec(float3(0.5, 0.5, 0.5));
	float3 worldCornerVec2 = objectToWorldVec(float3(-0.5, 0.5, 0.5));
	float3 worldCornerVec3 = objectToWorldVec(float3(0.5, -0.5, 0.5));
	float3 worldCornerVec4 = objectToWorldVec(float3(-0.5, -0.5, 0.5));
	float2 lengths1 = abs(float2(dot(worldCornerVec1, worldCameraDir), dot(worldCornerVec2, worldCameraDir)));
	float2 lengths2 = abs(float2(dot(worldCornerVec3, worldCameraDir), dot(worldCornerVec4, worldCameraDir)));
	float2 lengths = max(lengths1, lengths2);
	return max(lengths.x, lengths.y) * 2.0;
}

// 上記のように挟んだ時の平面の位置を求める
float2 getWorldBoundsNearFar(float worldBoundsDepth)
{
	float center = isPerspective()
		? distance(getWorldBoundsCenter(), getWorldCameraPos())
		: dot(getWorldBoundsCenter() - getWorldCameraPos(), getWorldCameraDir());
	return float2(-0.5, 0.5) * worldBoundsDepth + center;
}

// キューブの面に関するPlane.Raycastを3方向まとめて行う
float3 getLocalBoundsFaces(float3 localPos, float3 localViewDir, float faceOffset)
{
	float3 signs = sign(localViewDir);
	return -(signs * localPos + faceOffset) / (abs(localViewDir) + (1.0 - abs(signs)) * DIRECTIONAL_EPSILON);
}

// キューブ前面までの距離を求める
float getLocalBoundsFrontFace(float3 localPos, float3 localViewDir)
{
	float3 lengths = getLocalBoundsFaces(localPos, localViewDir, 0.5);
	return max(max(max(lengths.x, lengths.y), lengths.z), 0.0);
}

// デプステクスチャをもとに他の不透明オブジェクトのZ位置を算出する
// キューブ内に他の不透明オブジェクトが貫入している場合に対応するため使用する
float sampleOpaqueZ(float4 screenPos)
{
	float rawDepth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(screenPos));
	return isPerspective()
		? LinearEyeDepth(rawDepth)
		: -dot(unity_CameraInvProjection._33_34, float2(_ProjectionParams.x * (rawDepth * 2.0 - 1.0), 1.0));
}

fixed sample(float3 pos)
{
	fixed x = step(pos.x, _MaxX) * step(_MinX, pos.x);
	fixed y = step(pos.y, _MaxY) * step(_MinY, pos.y);
	fixed z = step(pos.z, _MaxZ) * step(_MinZ, pos.z);
	return tex3D(_Volume, pos).a * x * y * z;
}

v2f vert(appdata v)
{
	v2f o;
	o.vertex = UnityObjectToClipPos(v.vertex);
	o.worldPos = mul(unity_ObjectToWorld, v.vertex);
	o.localPos = v.vertex.xyz;
	o.worldViewDir = isPerspective() ? -UnityWorldSpaceViewDir(o.worldPos) : getWorldCameraDir();
	o.localViewDir = worldToObjectVec(o.worldViewDir);
	o.screenPos = ComputeScreenPos(o.vertex);
	return o;
}

fixed4 frag(v2f i) : SV_Target
{
	// カメラのニアプレーンから他の不透明オブジェクト表面までの範囲で、かつキューブ内である範囲を求める
	float3 worldViewDir = normalize(i.worldViewDir);
	float3 localViewDir = normalize(i.localViewDir);
	float peripheralFactor = 1.0 / dot(getWorldCameraDir(), worldViewDir);
	float3 worldCameraOrigin = getWorldCameraOrigin(i.worldPos);
	float3 localCameraOrigin = worldToObjectPos(worldCameraOrigin);
	float worldFrontFace = dot(objectToWorldVec(getLocalBoundsFrontFace(localCameraOrigin, localViewDir) * localViewDir), worldViewDir);
	float worldBackFace = dot(i.worldPos - worldCameraOrigin, worldViewDir);
	float worldCameraNear = getWorldCameraNear();
	float worldOpaque = sampleOpaqueZ(i.screenPos) * peripheralFactor;
	float worldEnter = max(worldFrontFace, worldCameraNear);
	float worldExit = min(worldBackFace, worldOpaque);

	// 不要な領域をクリッピングする
	clip(worldExit - worldEnter);

	// 視線方向の色積分の開始・終了ステップ番号を求める
	float worldBoundsDepth = getWorldBoundsDepth(getWorldCameraDir());
	float2 worldBoundsNear = getWorldBoundsNearFar(worldBoundsDepth).x;
	float worldLineOrigin = worldBoundsNear * peripheralFactor;
	float worldLineLength = worldBoundsDepth * peripheralFactor;
	float worldStepLength = worldLineLength / _Iteration;
	float worldLineOriginToEnter = worldEnter - worldLineOrigin;
	float worldLineOriginToExit = worldExit - worldLineOrigin;
	int startingIndex = (int)ceil(worldLineOriginToEnter / worldStepLength);
	int terminalIndex = (int)(worldLineOriginToExit / worldStepLength);

	float3 localStep = worldToObjectVec(worldStepLength * worldViewDir);
	float3 localLineOriginPos = worldToObjectPos(worldCameraOrigin + worldViewDir * worldLineOrigin);
	float4 output = 0.0;

	[loop]
	for (int j = startingIndex; j <= terminalIndex; j++)
	{
		float3 lpos = localLineOriginPos + j * localStep;
		fixed volume = sampleVolume(lpos + 0.5);
		fixed4 color = transferFunction(volume) * volume * _Intensity;
		output += (1.0 - output.a) * color;
	}

	return output;
}
	ENDCG

	SubShader
{
	Tags
	{
		"Queue" = "Transparent"
		"RenderType" = "Transparent"
	}

		Pass
	{
		Cull Front
		ZWrite Off
		ZTest LEqual
		Blend[_BlendSrc][_BlendDst]
		Lighting Off

		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		ENDCG
	}
}
}